package com.patternbox.eclipse.views;

import java.net.URL;
import java.util.ArrayList;
import java.util.ListIterator;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.eclipse.core.internal.runtime.Activator;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.jdt.core.IType;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.patternbox.eclipse.model.DesignPatternInstance;
import com.patternbox.eclipse.model.DesignPatternInstancePool;
import com.patternbox.eclipse.model.ScenarioInstance;
import com.patternbox.eclipse.model.ScenarioInstancePool;

public class AffectedPatternsView extends ViewPart {

	/**
	 * The ID of the view as specified by the extension.
	 */
	public static final String ID = "com.patternbox.eclipse.editor.views.AffectedPatternsView";

	private TableViewer viewer;
	private Action refreshAction;
	private Action doubleClickAction;
	private ArrayList<AffectedPattern> affectedDirectly;
	private Composite parent;
	 
	/**
	 * The constructor.
	 */
	public AffectedPatternsView() {
		
	}

	/**
	 * This is a callback that will allow us
	 * to create the viewer and initialize it.
	 */
	public void createPartControl(Composite parent) {
		
		this.parent = parent;
		
		createViewer(parent);
		
		loadViewer();		

		// Create the help context id for the viewer's control
		PlatformUI.getWorkbench().getHelpSystem().setHelp(viewer.getControl(), "com.patternbox.eclipse.editor.viewer");
		makeActions();
		hookContextMenu();
		//hookDoubleClickAction();
		contributeToActionBars();
	}

	/**
	 * Loads the viewer
	 */
	private void loadViewer() {
		
		DesignPatternInstancePool.initialize();
		affectedDirectly = new ArrayList<AffectedPattern>();
		
		Bundle pluginBundle = Activator.getDefault().getBundle("com.patternbox.eclipse.editor");
		
		URL impactFileUrl = pluginBundle.getEntry("/config/patternImpact.xml");			
		
		ArrayList<String> scenarioTypes = new ArrayList<String>();
		ArrayList<DesignPatternInstance> designPatternInstances = DesignPatternInstancePool.getInstances();
		
		try{		
			String impactFileURL = FileLocator.toFileURL(impactFileUrl).toString();	
				
			DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		
			Document impactDoc = dBuilder.parse(impactFileURL);
			impactDoc.getDocumentElement().normalize();
			
			// Load all the types of scenarios
			NodeList nodes = impactDoc.getElementsByTagName("scenario");
			
			// For each type of Scenario
			for (int i = 0; i < nodes.getLength(); i++) {
				Node node = nodes.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					Element element = (Element) node;
					scenarioTypes.add(getValue("id", element));
				}
			}				
			
			ScenarioInstancePool.initialize();
			ArrayList<String> scenarios = new ArrayList<String>();
			for (ScenarioInstance s : ScenarioInstancePool.getInstances()) {
				if (!scenarios.contains(s.getType())) {
					scenarios.add(s.getType());
				}
			}
			
			for (int i = 0; i < nodes.getLength(); i++) {
				Node node = nodes.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					Element element = (Element) node;
					String scenarioName = getValue("id", element);
					if ( scenarios.contains(scenarioName))
						loadAffectedPatterns(element, scenarioName, designPatternInstances);
				}
			}
		}
		catch (Exception e){
			e.printStackTrace();
		}		
		
		ArrayList<AffectedPattern> result = findIndirectlyAffectedPatterns(designPatternInstances);
		
		viewer.setInput(null);
		viewer.setInput(result);
	}
	
	private ArrayList<AffectedPattern> findIndirectlyAffectedPatterns(ArrayList<DesignPatternInstance> instancias) {
		
		ArrayList<AffectedPattern> result = (ArrayList<AffectedPattern>) affectedDirectly.clone();
		
		DesignPatternInstance instance;
		AffectedPattern affectedPattern;
		IType clase;
		DesignPatternInstance patternInstance;
		
		ListIterator<AffectedPattern> apIt = affectedDirectly.listIterator();
		ListIterator<IType> classesIt;
		ListIterator<DesignPatternInstance> instancesIt = instancias.listIterator();
		
		// Para cada instancia de patron afectada directamente
		while(apIt.hasNext()) {
			
			affectedPattern = apIt.next();
			instance = affectedPattern.getInstance();
			ArrayList<IType> classes = instance.getMembers();
			
			classesIt = classes.listIterator();
			//Para cada clase afectada directamente
			while(classesIt.hasNext()){
				clase = classesIt.next();
				
				while(instancesIt.hasNext()) {
					patternInstance = instancesIt.next();
					if (patternInstance != instance && patternInstance.hasMember(clase)) {
						AffectedPattern pattern = getAffectedPattern(patternInstance.getDesignPatternName());
						if (pattern == null){
							ArrayList<DesignPatternInstance> patInstance = getInstances(instancias, patternInstance.getDesignPatternName());
							for (DesignPatternInstance pInstance : patInstance) {
								AffectedPattern newPattern = new AffectedPattern(patternInstance.getDesignPatternName(), "Indirectly", affectedPattern.getAffectedBy(), pInstance);
								result.add(newPattern);
							}				
						}
						else{
							pattern.setAffectedBy(pattern.getAffectedBy().concat(", " + affectedPattern.getAffectedBy()));
						}
					}
				}
			}
		}
		return result;
	}

	private static String getValue(String tag, Element element) {
		NodeList nodes = element.getElementsByTagName(tag).item(0).getChildNodes();
		Node node = (Node) nodes.item(0);
		return node.getNodeValue();
	}
	
	private void loadAffectedPatterns(Element nodo, String scenario, ArrayList<DesignPatternInstance> instancias){
		NodeList patrones = nodo.getElementsByTagName("pattern");
		for( int i=0 ; i < patrones.getLength() ; i++){
			Element node = (Element) patrones.item(i);
			String value = node.getFirstChild().getNodeValue();
			AffectedPattern pattern = getAffectedPattern(value);
			if (pattern == null){
				ArrayList<DesignPatternInstance> patternInstances = getInstances(instancias, value);
				for (DesignPatternInstance patternInstance : patternInstances) {
					AffectedPattern newPattern = new AffectedPattern(value, "Directly", scenario, patternInstance);
					affectedDirectly.add(newPattern);
				}				
			}
			else{
				pattern.setAffectedBy(pattern.getAffectedBy().concat(", " + scenario));
			}
			
				
		}
	}
	
	private ArrayList<DesignPatternInstance> getInstances(ArrayList<DesignPatternInstance> list, String value){
		ArrayList<DesignPatternInstance> returnArray = new ArrayList<DesignPatternInstance>();
		ListIterator<DesignPatternInstance> it = list.listIterator();
		DesignPatternInstance instance;
		while(it.hasNext()) {  
			instance = it.next(); 
        	if(instance.getDesignPatternName().equals(value))
				returnArray.add(instance);
        }
		return returnArray;
	}
	
	private AffectedPattern getAffectedPattern(String patternName){
		ListIterator<AffectedPattern> it = affectedDirectly.listIterator();    
		AffectedPattern pattern;
		while(it.hasNext()) {  
        	pattern = it.next(); 
        	if(pattern.getPatternName().equals(patternName))
				return pattern;
        }
		return null;
	}
	
	/**
	 * Creates a viewer with 2 columns
	 * 
	 */
	private void createViewer(Composite parent) {
		viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		viewer.setContentProvider(ArrayContentProvider.getInstance());
				
		// First column is for the pattern name
		TableViewerColumn col = new TableViewerColumn(viewer, SWT.NONE);
		col.getColumn().setWidth(200);
		col.getColumn().setText("Patron");
		col.setLabelProvider(new ColumnLabelProvider() {
		  @Override
		  public String getText(Object element) {
			  AffectedPattern p = (AffectedPattern) element;
			  return p.getInstance().getInputFile().getName();
		  }
		});
		
		// Second column is for the pattern affection level
		TableViewerColumn col2 = new TableViewerColumn(viewer, SWT.NONE);
		col2.getColumn().setWidth(200);
		col2.getColumn().setText("Afectado");
		col2.setLabelProvider(new ColumnLabelProvider() {
		  @Override
		  public String getText(Object element) {
			  AffectedPattern p = (AffectedPattern) element;
			  return p.getTypeAffected();
		  }
		});
		
		// Third column is to see who affects the pattern
		TableViewerColumn col3 = new TableViewerColumn(viewer, SWT.NONE);
		col3.getColumn().setWidth(200);
		col3.getColumn().setText("Afectado por");
		col3.setLabelProvider(new ColumnLabelProvider() {
		  @Override
		  public String getText(Object element) {
			  AffectedPattern p = (AffectedPattern) element;
			  return p.getAffectedBy();
		  }
		});

		viewer.getTable().setHeaderVisible(true);
	}

	private void hookContextMenu() {
		MenuManager menuMgr = new MenuManager("#PopupMenu");
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				AffectedPatternsView.this.fillContextMenu(manager);
			}
		});
		Menu menu = menuMgr.createContextMenu(viewer.getControl());
		viewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	}

	private void contributeToActionBars() {
		IActionBars bars = getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
	}

	private void fillLocalPullDown(IMenuManager manager) {
	}

	private void fillContextMenu(IMenuManager manager) {
		manager.add(refreshAction);
	}
	
	private void fillLocalToolBar(IToolBarManager manager) {
		manager.add(refreshAction);
	}

	private void makeActions() {
		// Refresh view action 
		refreshAction = new Action() {
			public void run() {
				loadViewer();				
			}
		};
		refreshAction.setText("Refresh");
		refreshAction.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().
				getImageDescriptor(ISharedImages.IMG_ELCL_SYNCED));
		
//		doubleClickAction = new Action() {
//			public void run() {
//				ISelection selection = viewer.getSelection();
//				Object obj = ((IStructuredSelection)selection).getFirstElement();
//				showMessage("Double-click detected on "+obj.toString());
//			}
//		};
	}

//	private void hookDoubleClickAction() {
//		viewer.addDoubleClickListener(new IDoubleClickListener() {
//			public void doubleClick(DoubleClickEvent event) {
//				doubleClickAction.run();
//			}
//		});
//	}
	
	private void showMessage(String message) {
		MessageDialog.openInformation(
			viewer.getControl().getShell(),
			"Affected Patterns View",
			message);
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	public void setFocus() {
		viewer.getControl().setFocus();
	}
}